﻿
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using alib.Concurrency;
using alib.Debugging;
using alib.Math;
using alib.Enumerable;
using alib.Collections;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// interface for chart objects which are packed
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public interface IPacking : IChartObj
	{
		IReadOnlyList<IDerivation> Derivations { get; }
		List<IPacking> PackedEdges { get; }
		List<IPacking> UsurpPacking();
		void HoistPackingFrom(IPacking _pe_old);
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public struct PackingHelper
	{
		public List<IPacking> packed_edges;
		MemoizedItem<IReadOnlyList<IDerivation>> mpt;

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void HoistPackingFrom(IPacking pe_old)
		{
			Debug.Assert(mpt.IsNotStarted);
			Debug.Assert(pe_old.IsHoldOrRemove());
			Debug.Assert(packed_edges == null || packed_edges.Count > 0);

			List<IPacking> _old = pe_old.UsurpPacking();

			bool f_ahr = AnyHoldOrRemove(pe_old);

			if (_old != null)
			{
				if (packed_edges == null)
					packed_edges = _old;
				else
				{
					int c = _old.Count;
					for (int i = c - 1; i >= 0; i--)
					{
						var hoist = _old[i];
						if (AnyHoldOrRemove(hoist))
						{
							_old.RemoveAt(i);
							c--;
						}
						else
						{
							Debug.Assert(hoist.State() != AtomicSeqState.Transact);
							hoist.SetState(AtomicSeqState.Hold);
						}
					}
					if (c > 0)
						packed_edges.AddRange(_old);
				}
			}
			else if (f_ahr)
			{
				return;
			}
			else if (packed_edges == null)
			{
				packed_edges = new List<IPacking>(12);
			}

			packed_edges.Add(pe_old);
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// exhaustive unpacking of trees which are packed into this item with thread-safe cache
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public IReadOnlyList<IDerivation> Derivations(IPacking _this)
		{
			IReadOnlyList<IDerivation> r;
			if (mpt.FirstClaim())
			{
#if !DEBUG
				/// get rid of packed TFS body. todo: augment it with the restricted data
				((ArrayTfs)_this).Dealloc();
#endif
				_extract_helper eh = new _extract_helper
				{
					_this = _this,
					dtrs = _this.Daughters,
					packed_edges = this.packed_edges,
				};

				long c;
				try
				{
					c = eh._pre_count();
				}
				catch (Exception)
				{
					Console.WriteLine("mpt3");
					mpt.SetResult(null);
					throw;
				}
				if (c < 0)
					throw new Exception();

				//Debug.Print("{0}", c);

				//if (c > ctrl.chart.config.derivation_limit)
				//{
				//    mpt.SetResult(null);
				//    ctrl.CancelJob(new DerivationLimitExceededException("Derivation limit exceeded ({0:#,###})", c));
				//}

				if (c == 0)
				{
					/// this occurs when callers try to extract non-root (e.g. spanning) derivations for
					/// diagnostic purposes and they don't check the object's hold status first. Accordingly, 
					/// the item should always be in Hold state.
					if (!_this.IsHoldOrRemove())
						throw new Exception("-is-hold-or-remove-");

					r = Collection<IDerivation>.None;
				}
				else if (c == 1)
				{
					r = new[] { _this };
				}
				else
				{
					IDerivation[] arr = new IDerivation[c];
					try
					{
						foreach (var drv in eh._packed_trees())
							arr[--c] = drv;
					}
					catch (Exception ex)
					{
						Console.WriteLine("mpt4" + ex.ToString());
						mpt.SetResult(null);
						throw ex;
					}
#if DEBUG
					if (c != 0)
						throw new Exception();	/// mpt abandoned
#endif
					r = arr;
				}
				mpt.SetResult(r);
#if !DEBUG
				packed_edges = null;
#endif
			}
			else if ((r = mpt.Result) == null)
			{
				//_this.Control.CancelJob(null);
				throw new CancelJobException();
			}
			return r;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////
		static bool _any_dtrs_packed(IDerivation _this)
		{
			var rgd = _this.Daughters;
			for (int i = 0; i < rgd.Count; i++)
				if (rgd[i] is IPacking)
					return true;
			return false;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		struct _extract_helper
		{
			public IPacking _this;
			public IReadOnlyList<IDerivation> dtrs;
			public List<IPacking> packed_edges;

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// 
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public long _pre_count()
			{
				long est = AnyHoldOrRemove(_this) ? 0 : 1;
				if (est > 0)
					foreach (var dtr in _this.Daughters)
						if (dtr is IPacking)
							est *= ((IPacking)dtr).Derivations.Count;

				if (packed_edges != null)
					foreach (var peer in packed_edges)
						if (!AnyHoldOrRemove(peer))
							est += peer.Derivations.Count;

				return est;
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// when initiated with 'i_arg==0', enumerates the fixed-arity cross product of the packed trees 
			/// across each of this edge's chart daughters' unpacked derivations
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////
			IEnumerable<IReadOnlyList<IDerivation>> _rightwards(int i_arg)
			{
				IDerivation dtr = dtrs[i_arg++];
				return dtr is IPacking ? _next_p_cross_prod(i_arg, (IPacking)dtr) : _next_cross_prod(i_arg, dtr);
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// 
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			IEnumerable<IReadOnlyList<IDerivation>> _next_p_cross_prod(int i_arg, IPacking dtr)
			{
				foreach (var dd in dtr.Derivations)
					foreach (var cfg in _next_cross_prod(i_arg, dd))
						yield return cfg;
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// 
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			IEnumerable<IReadOnlyList<IDerivation>> _next_cross_prod(int i_arg, IDerivation dtr)
			{
				if (i_arg == dtrs.Count)
					yield return new[] { dtr };
				else
					foreach (var tree_xp in _rightwards(i_arg))
						yield return tree_xp.Prepend(dtr);
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// 
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public IEnumerable<IDerivation> _packed_trees()
			{
				/// trees postulated by the cross product of this item's daughters...
				if (!AnyHoldOrRemove(_this))
				{
					Debug.Assert(_this.Tfs.SemanticsRestricted);	/// otherwise you'd just use itself..?

					foreach (var dtrs in _rightwards(0))
					{
						IDerivation deriv;
						if (dtrs == _this.Daughters)
							deriv = _this;
						else
							deriv = new LazyTfsDerivation(_this, _this, dtrs);
						yield return deriv;
					}
				}

				/// ...plus subsumed structures packed into this item itself (and their subsumptions...)
				if (packed_edges != null)
					foreach (IPacking peer in packed_edges)
						if (!AnyHoldOrRemove(peer))
						{
							var pd = peer.Derivations;
							if (pd.Count != 0)
								foreach (var deriv in pd)
									yield return deriv;
							else
								yield return peer;
						}
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		static bool AnyHoldOrRemove(IDerivation d)
		{
			IAtomicSequence ias;
			for (int i = 0; i < d.Daughters.Count; i++)
				if ((ias = d.Daughters[i] as IAtomicSequence) != null && ias.IsHoldOrRemove())
					return true;
			return false;
		}
	};
}
